import numpy as np
import copy
import random
from population import Population

class CrossoverAndMutation(object):
    def __init__(self, individuals ,params):
        self.params = params
        self.individuals = individuals
        self.offsprings = []

    def process(self):
        crossover = Crossover(self.individuals, self.params)
        self.offsprings = crossover.do_crossover()
        for ones_index, ones_value in enumerate(self.offsprings):
            print('-----------------------------------------------------------------------------')
            for key, value in ones_value.items():
                if key == 'encode_layers':
                    continue
                elif key == 'acc':
                    value = float(value)
                print("%s:%s" % (key, value))
            print('-----------------------------------------------------------------------------')
        mutation = Mutation(self.offsprings, self.params)
        offsprings = mutation.do_mutation()

        return offsprings

class Crossover(object):
    def __init__(self, individuals, params):
        self.params = params
        self.individuals = individuals
        self.prob_crossover = params['prob_crossover']

        self.PU_maxlimit = params['PU_maxlimit']

        self.off_max_len = params['off_max_len']

    def _choose_one_parent(self):
        count_ = len(self.individuals)
        idx1 = int(np.floor(np.random.random() * count_))
        idx2 = int(np.floor(np.random.random() * count_))
        while idx2 == idx1:
            idx2 = int(np.floor(np.random.random() * count_))

        if self.individuals[idx1]['acc'] > self.individuals[idx2]['acc']:
            return idx1
        else:
            return idx2
    """
    binary tournament selection
    """
    def _choose_two_diff_parents(self):
        idx1 = self._choose_one_parent()
        idx2 = self._choose_one_parent()
        while idx2 == idx1:
            idx2 = self._choose_one_parent()  #If population only have two individuals with different accuracy,the algorithms will stuck into endless loop

        assert idx1 < len(self.individuals)
        assert idx2 < len(self.individuals)
        return idx1, idx2

    def cross_one(self , source1, source2, position1, position2):#Notice slice[0:-1] equal to discard the last element
        derive1_source = source1[0:position1]
        derive1_source.extend(source2[position2:])

        derive2_source = source2[0:position2]
        derive2_source.extend(source1[position1:])
        return derive1_source , derive2_source

    def modify_off_channel(self , offspring_dict):
        off_sequence = offspring_dict['order_type']
        off_inchannels = offspring_dict['inchannels']
        off_outchannels = offspring_dict['outchannels']
        off_BU_outchannels = offspring_dict['BU_outchannels']
        if len(off_sequence)== 1:
            if off_sequence[0] == 1:
                off_outchannels[0] = off_BU_outchannels[0]
            elif off_sequence[0] == 3:
                off_outchannels[0] = off_inchannels[0]
        elif len(off_sequence) > 1:
            for _index, _type  in enumerate(off_sequence):
                if _index < int(len(off_sequence) - 1):
                    if _type == 1 :
                        off_outchannels[_index] = off_BU_outchannels[_index]
                        off_inchannels[_index+1] = off_outchannels[_index]
                    elif _type == 2 or _type == 3:
                        off_outchannels[_index] = off_inchannels[_index]
                        off_inchannels[_index + 1] = off_outchannels[_index]
                elif _index == int(len(off_sequence) - 1):
                    if _type == 1 :
                        off_outchannels[_index] = off_BU_outchannels[_index]
                    elif _type == 2 or _type == 3:
                        off_outchannels[_index] = off_inchannels[_index]

        offspring_dict['inchannels'] = off_inchannels
        offspring_dict['outchannels'] = off_outchannels
        return offspring_dict

    def reload_off_information(self ,parent1_dict ,parent2_dict ,cp_x1 , cp_x2 ,off1_sequence,off2_sequence):
        offspring1_dict , offspring2_dict = {} , {}
        off1_inchannels, off2_inchannels = self.cross_one(parent1_dict['inchannels'],parent2_dict['inchannels'],
                                                          cp_x1,cp_x2)
        off1_outchannels, off2_outchannels = self.cross_one(parent1_dict['outchannels'],parent2_dict['outchannels'],
                                                          cp_x1,cp_x2)
        off1_pooltype, off2_pooltype = self.cross_one(parent1_dict['pool_type'], parent2_dict['pool_type'],
                                                           cp_x1,cp_x2)
        off1_BasicBlock_amount, off2_BasicBlock_amount = self.cross_one(parent1_dict['BasicBlock_amount'], parent2_dict['BasicBlock_amount'],
                                                        cp_x1, cp_x2)
        off1_BU_outchannels, off2_BU_outchannels = self.cross_one(parent1_dict['BU_outchannels'], parent2_dict['BU_outchannels'],
                                                        cp_x1, cp_x2)
        off1_BU_conv_type, off2_BU_conv_type = self.cross_one(parent1_dict['BU_conv_type'], parent2_dict['BU_conv_type'],cp_x1, cp_x2)
        off1_BU_kernel_size, off2_BU_kernel_size = self.cross_one(parent1_dict['BU_kernel_size'], parent2_dict['BU_kernel_size'], cp_x1, cp_x2)

        off1_AU_conv_type, off2_AU_conv_type = self.cross_one(parent1_dict['AU_conv_type'],
                                                              parent2_dict['AU_conv_type'], cp_x1, cp_x2)

        offspring1_dict['inchannels'] = off1_inchannels
        offspring1_dict['outchannels'] = off1_outchannels
        offspring1_dict['pool_type'] = off1_pooltype
        offspring1_dict['BasicBlock_amount'] = off1_BasicBlock_amount
        offspring1_dict['BU_outchannels'] = off1_BU_outchannels
        offspring1_dict['BU_conv_type'] = off1_BU_conv_type
        offspring1_dict['BU_kernel_size'] = off1_BU_kernel_size
        offspring1_dict['AU_conv_type'] = off1_AU_conv_type

        offspring1_dict['acc'] = -1.0
        offspring1_dict['size'] = []
        offspring1_dict['encode_layers'] = []
        offspring1_dict['order_type'] = off1_sequence

        # To load off2 variable changed from parents
        offspring2_dict['inchannels'] = off2_inchannels
        offspring2_dict['outchannels'] = off2_outchannels
        offspring2_dict['pool_type'] = off2_pooltype
        offspring2_dict['BasicBlock_amount'] = off2_BasicBlock_amount
        offspring2_dict['BU_outchannels'] = off2_BU_outchannels
        offspring2_dict['BU_conv_type'] = off2_BU_conv_type
        offspring2_dict['BU_kernel_size'] = off2_BU_kernel_size
        offspring2_dict['AU_conv_type'] = off2_AU_conv_type

        offspring2_dict['acc'] = -1.0
        offspring2_dict['size'] = []
        offspring2_dict['encode_layers'] = []
        offspring2_dict['order_type'] = off2_sequence
        return offspring1_dict , offspring2_dict

    def do_crossover(self):
        new_offspring_list = []
        with open("Individuals_train_log.txt", "a") as f_:
            for _ in range( len(self.individuals) // 2):  #如果总数是4,进行2次循环，每次交叉生成2个新个体，那就是最终总数保持了4个
                ind1, ind2 = self._choose_two_diff_parents()
                print("For this crossover, the %s individual and %s individual are selected"%((ind1+1),(ind2+1)))
                f_.write("For this crossover, the %s individual and %s individual are selected"%((ind1+1),(ind2+1)) + '\n')
                parent1_dict, parent2_dict = copy.deepcopy(self.individuals[ind1]), copy.deepcopy(self.individuals[ind2])  # deepcopy
                parent1 , parent2 = parent1_dict['order_type'], parent2_dict['order_type']
                p_ = random.random() # random float,range: 0 <= n < 1.0
                if p_ <= self.prob_crossover:
                    """
                    exchange their units from these parent individuals, the exchanged units must satisfy
                    --- the number of pooling layer should not be more than the predefined setting
                    --- if their is no changing after this crossover, keep the original acc -- a mutation should be given [to do---]
                    """
                    #selected parents generate offspring
                    count_parent1 , count_parent2 = len(parent1) , len(parent2)
                    alert_num = 0
                    while True:
                        alert_num+=1
                        if alert_num > 10000:  #Two individual no cross position,break cycle Currently
                            break
                        cp_x1 = int(np.floor(np.random.random() * count_parent1))  # This is guarantee cross position
                        cp_x2 = int(np.floor(np.random.random() * count_parent2))
                        if cp_x1 != 0 and cp_x2 != 0: #Firstly, two index can't equal to zero
                            off1_sequence, off2_sequence = self.cross_one(parent1, parent2, cp_x1, cp_x2)
                            off1_num_PU , off2_num_PU = 0 , 0
                            for type1 in off1_sequence:
                                if type1 == 2:
                                    off1_num_PU += 1
                            for type2 in off2_sequence:
                                if type2 == 2:
                                    off2_num_PU += 1
                                #if pool units and sequence length surpass limit ,cycle will reselect cross position
                                #Notice , we doesn't concern min Pool unit ,so that offspring Pool units are possible to less than 4(PU_limit)
                            if (off1_num_PU <= int(self.PU_maxlimit)) and (off2_num_PU <= int(self.PU_maxlimit)):
                                if (int(len(off1_sequence)) <= int(self.off_max_len)) and (int(len(off2_sequence)) <= int(self.off_max_len)):
                                    break
                    print("The position of the intersection of two individuals is respectively %s and %s of the individual list index"%(cp_x1,cp_x2))
                    f_.write("The position of the intersection of two individuals is respectively %s and %s of the individual list index"%(cp_x1,cp_x2) + '\n')
                    if alert_num > 10000: # Two individual forbid cross position ,continue cycle Currently
                        continue
                    # To load off1 and off2 variable after cross possess in dictionary
                    offspring1_dict, offspring2_dict = self.reload_off_information(parent1_dict, parent2_dict, cp_x1, cp_x2,
                                                                                   off1_sequence, off2_sequence)
                    # Modify offspring channels information after crossed
                    offspring1_dict , offspring2_dict = self.modify_off_channel(offspring1_dict) , self.modify_off_channel(offspring2_dict)

                    new_offspring_list.append(offspring1_dict) , new_offspring_list.append(offspring2_dict)
                else:
                    new_offspring_list.append(parent1_dict), new_offspring_list.append(parent2_dict)
                    print("Less than the probability threshold, the selected individuals were not crossed")
                    f_.write("Less than the probability threshold, the selected individuals were not crossed" + '\n')
            f_.write('\n')
            f_.flush()
        return new_offspring_list

class Mutation(object):
    # ['ADD','REMOVE','ALTER']
    def __init__(self, individuals , params):
        self.individuals = individuals
        self.params = params
        self.prob_mutation = params['prob_mutation']
        self.BasicBlock_min_limit = params['BasicBlock_min_limit']
        self.BasicBlock_max_limit = params['BasicBlock_max_limit']
        self.BU_MinOutChannels_list = params['BU_MinOutChannels_list']
        self.BU_MaxOutChannels_list = params['BU_MaxOutChannels_list']
        # self.BU_outchannels_list = params['BU_outchannels_list']
        self.BU_conv_type_list = params['BU_conv_type_list']
        self.BU_kernel_size_list = params['BU_kernel_size_list']

        self.AU_conv_type_list = params['AU_conv_type_list']

        self.PU_maxlimit = params['PU_maxlimit']

        self.init_inchannels = params['init_inchannels']
        self.off_max_len = params['off_max_len']

        # self.Max_out_channels = params['Max_out_channels']
        self.mutation_list = [0.25, 0.25, 0.25, 0.25]  #[0.25, 0.25, 0.25, 0.25]实际这里是参数，等会要放到参数表里去[0.333, 0.333, 0.333 , 0]

    def do_mutation(self):
        through_offspring = []
        new_and_keep_off_list = []
        count_ = 0
        with open("Individuals_train_log.txt", "a") as f_:
            print('----------------------------------------------------------------------------------------------'+'\n')
            f_.write('----------------------------------------------------------------------------------------------' + '\n')
            f_.flush()
            #看交叉还是变异，存在一个死循环
            for individual in self.individuals:
                count_ += 1
                p_ = random.random()
                if p_ <= self.prob_mutation:  # 变异阈值  用于生成一个0到1的随机符点数: 0 <= n < 1.0
                    flag = 0 #控制最外层的while退出
                    while True:
                        mutation_type = self.select_roulette(self.mutation_list)  # 从某种意义来说，应该在考虑完mutation_type这里就该考虑好变异位置
                        if mutation_type == 0 and int(len(individual['order_type'])) < int(self.off_max_len):
                            flag = 1
                        elif mutation_type == 1:
                            cache_remove_index_list = []   #完全遍历整体位置才能弹出
                            while True:
                                remove_position = int(np.floor(np.random.random() * len(individual['order_type'])))
                                if remove_position in cache_remove_index_list:
                                    continue
                                if individual['order_type'][remove_position] != 2:  #Don't allow to remove pool_unit and first unit with second pool unit
                                    if remove_position != 0:
                                        flag = 1
                                        break
                                    elif remove_position == 0 and individual['order_type'][1] != 2:
                                        flag = 1
                                        break
                                if remove_position not in cache_remove_index_list:
                                    cache_remove_index_list.append(remove_position)
                                if len(cache_remove_index_list) == len(individual['order_type']):
                                        break
                        elif mutation_type == 2:
                            flag = 1
                        elif mutation_type == 3:
                            cache_pos_selected_index_list = []
                            cache_pos_select_index_list = []
                            while True:
                                pos_selected = int(np.floor(np.random.random() * len(individual['order_type'])))
                                pos_select = int(np.floor(np.random.random() * len(individual['order_type'])))
                                if (pos_selected in cache_pos_selected_index_list) and (pos_select in cache_pos_select_index_list):
                                    continue
                                if pos_selected != pos_select:
                                    if (individual['order_type'][pos_selected] != 2 or pos_select != 0) and (individual['order_type'][pos_select] != 2 or pos_selected != 0):
                                        flag = 1
                                        break
                                if (pos_selected not in cache_pos_selected_index_list) or (pos_select not in cache_pos_select_index_list):
                                    cache_pos_selected_index_list.append(pos_selected)
                                    cache_pos_select_index_list.append(pos_select)
                                if len(cache_pos_selected_index_list) == len(individual['order_type']) and len(cache_pos_select_index_list) == len(individual['order_type']):
                                        break
                        if flag == 1:
                            break
                    print("After cross，the " + str(count_) + "st inviduals from offsprings come into mutation")
                    f_.write("After cross，the " + str(count_) + "st inviduals from offsprings come into mutation" + '\n')
                    f_.flush()
                    if mutation_type == 0:
                        print('Do the ADD mutation')
                        through_offspring = self.add_unit(individual)
                    elif mutation_type == 1:
                        print('Do the REMOVE mutation')
                        through_offspring = self.remove_unit(individual , remove_position)
                    elif mutation_type == 2:
                        print('Do the INNER ALTER mutation')
                        through_offspring = self.alter_unit(individual)
                    elif mutation_type == 3 :
                        print('Do the EXCHANGE mutation')
                        through_offspring = self.exchange_unit(individual, pos_select , pos_selected)
                    new_and_keep_off_list.append(through_offspring)
                else:
                    print("After cross，the" + str(count_) + "st inviduals from offsprings failed to mutate" + '\n')
                    f_.write("After cross，the" + str(count_) + "st inviduals from offsprings failed to mutate" + '\n')
                    f_.flush()
                    new_and_keep_off_list.append(individual)
        return new_and_keep_off_list

    def add_unit(self , individual):    #select add unit type
        with open("Individuals_train_log.txt", "a") as f_:
            f_.write('Do the ADD mutation' + '\n')
            num_PU = 0
            for _type_ in individual['order_type']:
                if _type_ == 2:
                    num_PU += 1
            u_ = random.random()
            if u_ < 0.3333: select_type = 1
            elif u_ >= 0.3333 and u_ < 0.6666: select_type = 2
            else: select_type = 3

            if select_type == 2 and num_PU >= self.PU_maxlimit:
                reset_ = random.random()
                select_type = (1 if reset_ < 0.5 else 3)

            while True:
                add_position = int(np.floor(np.random.random() * len(individual['order_type'])))
                if select_type != 2 or add_position != 0: # Only select_type==2 and mutation_position==0 shuffle mutation position
                    f_.write("Add position is "+ str(add_position) + '\n')
                    print("Add position is "+ str(add_position))
                    break
            if select_type == 1:  ##变异加是在这选中的位置前面加，交叉是在选中的点位置后面进行交叉
                f_.write('Add BU' + '\n')
                print('Add BU')
                if add_position != int(len(individual['order_type']) - 1):
                    individual['order_type'].insert(add_position, 1)
                    # individual['BasicBlock_amount'].insert(add_position, individual['BasicBlock_amount'][add_position])
                    # individual['BU_outchannels'].insert(add_position, individual['BU_outchannels'][add_position])
                    # individual['BU_conv_type'].insert(add_position, individual['BU_conv_type'][add_position])
                    # individual['BU_kernel_size'].insert(add_position, individual['BU_kernel_size'][add_position])

                    individual['BasicBlock_amount'].insert(add_position, np.random.randint(self.BasicBlock_min_limit, self.BasicBlock_max_limit + 1))
                    individual['BU_outchannels'].insert(add_position, individual['outchannels'][add_position])
                    individual['BU_conv_type'].insert(add_position, self.BU_conv_type_list[np.random.randint(0, len(self.BU_conv_type_list))])
                    individual['BU_kernel_size'].insert(add_position,self.BU_kernel_size_list[np.random.randint(0, len(self.BU_kernel_size_list))])

                    individual['AU_conv_type'].insert(add_position, individual['AU_conv_type'][add_position])
                    individual['pool_type'].insert(add_position, individual['pool_type'][add_position])
                    # in the first
                    if add_position == 0:
                        individual['inchannels'].insert(add_position, self.init_inchannels)
                        individual['outchannels'].insert(add_position, individual['inchannels'][add_position + 1])
                    # in the medium
                    elif add_position != 0 :
                        individual['inchannels'].insert(add_position, individual['outchannels'][add_position - 1])
                        individual['outchannels'].insert(add_position, individual['inchannels'][add_position + 1])
                # in the end
                elif add_position == int(len(individual['order_type']) - 1):
                    individual['order_type'].append(1)# append
                    # individual['BasicBlock_amount'].append(individual['BasicBlock_amount'][add_position])
                    # individual['BU_outchannels'].append(individual['BU_outchannels'][add_position])
                    # individual['BU_conv_type'].append(individual['BU_conv_type'][add_position])
                    # individual['BU_kernel_size'].append(individual['BU_kernel_size'][add_position])
                    individual['BasicBlock_amount'].append(np.random.randint(self.BasicBlock_min_limit,self.BasicBlock_max_limit + 1))
                    individual['BU_outchannels'].append(individual['outchannels'][add_position])
                    individual['BU_conv_type'].append(self.BU_conv_type_list[np.random.randint(0, len(self.BU_conv_type_list))])
                    individual['BU_kernel_size'].append(self.BU_kernel_size_list[np.random.randint(0, len(self.BU_kernel_size_list))])

                    individual['AU_conv_type'].append(individual['AU_conv_type'][add_position])

                    individual['pool_type'].append(individual['pool_type'][add_position])
                    individual['inchannels'].append(individual['outchannels'][add_position])
                    individual['outchannels'].append(individual['inchannels'][add_position + 1])
            elif select_type == 2:
                f_.write('Add PU' + '\n')
                print('Add PU')
                # in the medium, Ban first position is PU
                if add_position != int(len(individual['order_type']) - 1):
                    individual['order_type'].insert(add_position, 2)
                    individual['BasicBlock_amount'].insert(add_position, individual['BasicBlock_amount'][add_position])
                    individual['BU_outchannels'].insert(add_position, individual['BU_outchannels'][add_position])
                    individual['BU_conv_type'].insert(add_position, individual['BU_conv_type'][add_position])
                    individual['BU_kernel_size'].insert(add_position, individual['BU_kernel_size'][add_position])

                    individual['AU_conv_type'].insert(add_position, individual['AU_conv_type'][add_position])

                    # individual['pool_type'].insert(add_position, individual['pool_type'][add_position])
                    individual['pool_type'].insert(add_position, np.random.randint(1, 3))
                    individual['inchannels'].insert(add_position, individual['outchannels'][add_position - 1])
                    individual['outchannels'].insert(add_position, individual['inchannels'][add_position + 1])

                # in the end
                elif add_position == int(len(individual['order_type']) - 1):
                    individual['order_type'].append(2)
                    individual['BasicBlock_amount'].append(individual['BasicBlock_amount'][add_position])
                    individual['BU_outchannels'].append(individual['BU_outchannels'][add_position])
                    individual['BU_conv_type'].append(individual['BU_conv_type'][add_position])
                    individual['BU_kernel_size'].append(individual['BU_kernel_size'][add_position])

                    individual['AU_conv_type'].append(individual['AU_conv_type'][add_position])

                    # individual['pool_type'].append(individual['pool_type'][add_position])
                    individual['pool_type'].append(np.random.randint(1, 3))
                    individual['inchannels'].append(individual['outchannels'][add_position])
                    individual['outchannels'].append(individual['inchannels'][add_position + 1])
            elif select_type == 3:
                f_.write('Add AU' + '\n')
                print('Add AU')
                if add_position != int(len(individual['order_type']) - 1):
                    individual['order_type'].insert(add_position, 3)
                    individual['BasicBlock_amount'].insert(add_position, individual['BasicBlock_amount'][add_position])
                    individual['BU_outchannels'].insert(add_position, individual['BU_outchannels'][add_position])
                    individual['BU_conv_type'].insert(add_position, individual['BU_conv_type'][add_position])
                    individual['BU_kernel_size'].insert(add_position, individual['BU_kernel_size'][add_position])

                    # individual['AU_conv_type'].insert(add_position, individual['AU_conv_type'][add_position])
                    individual['AU_conv_type'].insert(add_position,self.AU_conv_type_list[np.random.randint(0, len(self.AU_conv_type_list))])
                    individual['pool_type'].insert(add_position, individual['pool_type'][add_position])

                    # in the first
                    if add_position == 0:
                        individual['inchannels'].insert(add_position, self.init_inchannels)
                        individual['outchannels'].insert(add_position, self.init_inchannels)
                    # in the medium
                    elif add_position != 0:
                        individual['inchannels'].insert(add_position, individual['outchannels'][add_position - 1])
                        individual['outchannels'].insert(add_position, individual['inchannels'][add_position + 1])

                # in the end
                elif add_position == int(len(individual['order_type']) - 1):
                    individual['order_type'].append(3)
                    individual['BasicBlock_amount'].append(individual['BasicBlock_amount'][add_position])
                    individual['BU_outchannels'].append(individual['BU_outchannels'][add_position])
                    individual['BU_conv_type'].append(individual['BU_conv_type'][add_position])
                    individual['BU_kernel_size'].append(individual['BU_kernel_size'][add_position])

                    # individual['AU_conv_type'].append(individual['AU_conv_type'][add_position])
                    individual['AU_conv_type'].append(self.AU_conv_type_list[np.random.randint(0, len(self.AU_conv_type_list))])

                    individual['pool_type'].append(individual['pool_type'][add_position])
                    individual['inchannels'].append(individual['outchannels'][add_position])
                    individual['outchannels'].append(individual['inchannels'][add_position + 1])
            f_.write('\n')
            f_.flush()
        return individual

    def remove_unit(self , individual , remove_position ):
        with open("Individuals_train_log.txt", "a") as f_:
            f_.write('Do the REMOVE mutation' + '\n')
            print("remove position is " + str(remove_position))
            f_.write("remove position is " + str(remove_position) + '\n')
            print('Remove ' + str(individual['order_type'][remove_position]) + ' unit')
            f_.write('Remove ' + str(individual['order_type'][remove_position]) + ' unit'+'\n')
            del individual['pool_type'][remove_position]
            del individual['BasicBlock_amount'][remove_position]
            del individual['BU_outchannels'][remove_position]
            del individual['BU_conv_type'][remove_position]
            del individual['BU_kernel_size'][remove_position]
            del individual['AU_conv_type'][remove_position]
            del individual['order_type'][remove_position]
            del individual['inchannels'][remove_position]
            del individual['outchannels'][remove_position]
            individual = self.modify_channel(individual)  # modify channel
            f_.write('\n')
            f_.flush()
        return individual

    def exchange_unit(self, individual, pos_select , pos_selected):
        with open("Individuals_train_log.txt", "a") as f_:
            f_.write('Do the EXCHANGE mutation ' + '\n')
            print("The selected mutant selected gene sequence position% s and position% s for exchange" % (pos_select , pos_selected))
            f_.write("The selected mutant selected gene sequence position %s and position %s for exchange" % (pos_select , pos_selected)+'\n')
            individual['pool_type'][pos_select],individual['pool_type'][pos_selected] = individual['pool_type'][pos_selected],individual['pool_type'][pos_select]
            individual['BasicBlock_amount'][pos_select],individual['BasicBlock_amount'][pos_selected] = individual['BasicBlock_amount'][pos_selected],individual['BasicBlock_amount'][pos_select]
            individual['BU_outchannels'][pos_select], individual['BU_outchannels'][pos_selected] = individual['BU_outchannels'][pos_selected],individual['BU_outchannels'][pos_select]
            individual['BU_conv_type'][pos_select], individual['BU_conv_type'][pos_selected] = individual['BU_conv_type'][pos_selected], individual['BU_conv_type'][pos_select]
            individual['BU_kernel_size'][pos_select], individual['BU_kernel_size'][pos_selected] = individual['BU_kernel_size'][pos_selected], individual['BU_kernel_size'][pos_select]
            individual['AU_conv_type'][pos_select], individual['AU_conv_type'][pos_selected] = individual['AU_conv_type'][pos_selected], individual['AU_conv_type'][pos_select]

            individual['order_type'][pos_select],individual['order_type'][pos_selected] = individual['order_type'][pos_selected],individual['order_type'][pos_select]
            individual['inchannels'][pos_select],individual['inchannels'][pos_selected] = individual['inchannels'][pos_selected],individual['inchannels'][pos_select]
            individual['outchannels'][pos_select],individual['outchannels'][pos_selected] = individual['outchannels'][pos_selected],individual['outchannels'][pos_select]
            individual = self.modify_channel(individual)
            f_.write('\n')
            f_.flush()
        return individual

    def alter_unit(self , individual):
        with open("Individuals_train_log.txt", "a") as f_:
            f_.write('Do the INNER ALTER mutation' + '\n')
            alter_position = int(np.floor(np.random.random() * len(individual['order_type'])))
            alter_unit = individual['order_type'][alter_position]
            print("Alter position is " + str(alter_position))
            f_.write("Alter position is " + str(alter_position) + '\n')
            f_.flush()
            if alter_unit == 1:
                individual = self.do_alter_BU_mutation(individual, alter_position)
            elif alter_unit == 2:
                individual = self.do_alter_PU_mutation(individual, alter_position)
            elif alter_unit == 3:
                individual = self.do_alter_AU_mutation(individual, alter_position)
        return individual

    def do_alter_BU_mutation(self,individual, alter_position):
        with open("Individuals_train_log.txt", "a") as f_:
            f_.write("Alter BU " + '\n')
            a_ = random.random()  #Select alter unit channel or RBamount
            if a_ < 0.25:
                print('Alter BU outchannel')
                f_.write("Alter BU outchannel" + '\n')
                individual['BU_outchannels'][alter_position] = np.random.randint(self.BU_MinOutChannels_list, self.BU_MaxOutChannels_list)
                individual['outchannels'][alter_position] = individual['BU_outchannels'][alter_position]
                individual = self.modify_channel( individual)
            elif a_ >= 0.25 and a_ < 0.5:
                print('Alter BasicBlock_amount')
                f_.write("Alter BasicBlock_amount" + '\n')
                individual['BasicBlock_amount'][alter_position] = np.random.randint(self.BasicBlock_min_limit, self.BasicBlock_max_limit + 1 )
            elif a_ >= 0.5 and a_ < 0.75:
                print('Alter BU_conv_type')
                f_.write("Alter BU_conv_type" + '\n')
                individual['BU_conv_type'][alter_position] = self.BU_conv_type_list[np.random.randint(0, len(self.BU_conv_type_list))]
            else:
                print('Alter BU_kernel_size')
                f_.write("Alter BU_kernel_size" + '\n')
                individual['BU_kernel_size'][alter_position] = self.BU_kernel_size_list[np.random.randint(0, len(self.BU_kernel_size_list))]
            f_.write('\n')
            f_.flush()
        return individual

    def do_alter_PU_mutation(self,individual, alter_position):
        with open("Individuals_train_log.txt", "a") as f_:
            print('Alter PU ')
            f_.write("Alter PU " + '\n')
            print('Alter PU pooltype')
            f_.write('alter PU pooltype' + '\n')
            max_or_avg = random.random()
            if max_or_avg > 0.5:
                individual['pool_type'][alter_position] = 1
            else:
                individual['pool_type'][alter_position] = 2
            f_.write('\n')
            f_.flush()
        return individual

    def do_alter_AU_mutation(self,individual, alter_position):
        with open("Individuals_train_log.txt", "a") as f_:
            print('Alter AU conv type')
            f_.write("Alter AU conv type" + '\n')
            individual['AU_conv_type'][alter_position] = self.AU_conv_type_list[np.random.randint(0, len(self.AU_conv_type_list))]
            f_.write('\n')
            f_.flush()
        return individual

    def modify_channel(self, individual):  # manipulate_position to set where the channel starts
        if len(individual['order_type']) == 1:
            if int(individual['inchannels'][0]) != self.init_inchannels:
                individual['inchannels'][0] = self.init_inchannels
                if individual['order_type'][0] == 1:
                    individual['outchannels'][0] = individual['BU_outchannels'][0]
                elif individual['order_type'][0] == 3:
                    individual['outchannels'][0] = self.init_inchannels
        elif len(individual['order_type']) > 1:
            for _index, _type in enumerate(individual['order_type']):
                if _index == 0:
                    if int(individual['inchannels'][_index]) != self.init_inchannels:
                        individual['inchannels'][_index] = self.init_inchannels
                    if _type == 1:
                        individual['outchannels'][_index] = individual['BU_outchannels'][_index]
                    elif _type == 3:
                        individual['outchannels'][_index] = self.init_inchannels
                    individual['inchannels'][_index + 1] = individual['outchannels'][_index]
                elif _index < int(len(individual['order_type']) - 1) and (_index > 0):
                    if _type == 1:
                        individual['outchannels'][_index] = individual['BU_outchannels'][_index]
                        individual['inchannels'][_index + 1] = individual['outchannels'][_index]
                    elif _type == 2 or _type == 3:
                        individual['outchannels'][_index] = individual['inchannels'][_index]
                        individual['inchannels'][_index + 1] = individual['outchannels'][_index]
                elif _index == int(len(individual['order_type']) - 1):
                    if _type == 1:
                        individual['outchannels'][_index] = individual['BU_outchannels'][_index]
                    elif _type == 2 or _type == 3:
                        individual['outchannels'][_index] = individual['inchannels'][_index]
        return individual

    def select_roulette(self , _a):
        a = np.asarray(_a)
        k = 1
        idx = np.argsort(a)
        idx = idx[::-1]
        sort_a = a[idx]
        sum_a = np.sum(a).astype(np.float)
        selected_index = []
        for i in range(k):
            u = np.random.rand() * sum_a
            sum_ = 0
            for i in range(sort_a.shape[0]):
                sum_ += sort_a[i]
                if sum_ > u:
                    selected_index.append(idx[i])
                    break
        return selected_index[0]



if __name__ == '__main__':  #test crossandmutation
    params = {
        'prob_crossover': 0.9,  # 0.9
        'prob_mutation': 0.2,  # 0.2 #to test mutation component

        'BasicBlock_min_limit': 3,  # 3 10
        'BasicBlock_max_limit': 10,  # 即最大生成10个ResBlock
        # 'BU_outchannels_list': [64, 128, 256],
        'BU_MinOutChannels_list': 64,
        'BU_MaxOutChannels_list': 256,
        'BU_conv_type_list': [1, 2],  # 1 and 2 is symbol of Dilconv and Sepconv respectively
        'BU_kernel_size_list': [3, 5],

        'BU_minlimit': 1,
        'BU_maxlimit': 4,

        'PU_minlimit': 1,
        'PU_maxlimit': 4,  # 这里估计要改，调参的话，现在池化层是   size/2

        'AU_minlimit': 1,
        'AU_maxlimit': 4,
        'AU_conv_type_list': [0, 1],

        'init_max_len': 12,
        'off_max_len': 12,  # 应该设大点比如15或者20
        'init_inchannels': 200,
        # 'Max_out_channels': 64,  # 用在首序列初始化和RB的内部变异

        'init_size': 32,  # 64  32
        'pop_size': 20,
        'max_gen': 10,
        'EPOCH': 10,  # 150
        'BATCH_SIZE': 32,
        'num_class': 16,
    }
    # 模型定义-GlobalNet
    population = Population(params)  # 创建种群的实例
    individuals_information = population.initialize_individuals()
    for ones_index, ones_value in enumerate(individuals_information):
        print('-----------------------------------------------')
        for key, value in ones_value.items():
            if key == 'encode_layers':
                continue
            elif key == 'acc':
                value = float(value)
            print("%s:%s" % (key, value))
        print('-----------------------------------------------------------------------------')
    test_crossover_mutation = CrossoverAndMutation(individuals_information , params )
    off = test_crossover_mutation.process()
    for ones_index, ones_value in enumerate(off):
        print('-----------------------------------------------')
        for key, value in ones_value.items():
            if key == 'encode_layers':
                continue
            elif key == 'acc':
                value = float(value)
            print("%s:%s" % (key, value))
        print('-----------------------------------------------------------------------------')

